package org.vossnet.sfig;

import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;
import org.antlr.runtime.tree.TreeNodeStream;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.apache.log4j.Logger;
import static org.apache.log4j.Logger.getLogger;
import static java.lang.String.format;
import java.util.concurrent.atomic.AtomicInteger;


public class SFigBeanDefinitionReader extends AbstractBeanDefinitionReader {
    private static final Logger log = getLogger(SFigBeanDefinitionReader.class);
    protected final GenericApplicationContext ctx;

    public SFigBeanDefinitionReader(GenericApplicationContext ctx) { super(ctx); this.ctx = ctx; }

    @Override
    public int loadBeanDefinitions(Resource rsrc) throws BeanDefinitionStoreException {
        final ClassPathResource clsPathRsrc = rsrc instanceof ClassPathResource ? (ClassPathResource) rsrc : null;
        if (clsPathRsrc == null) {
            throw new BeanDefinitionStoreException(
                    format("SFig only supports %s Spring resource i/o type",ClassPathResource.class.getSimpleName()) );
        }
        RunFig runFig = null;
        try {
            runFig = new RunFig(clsPathRsrc);
            return forEach(runFig);
        } catch (Exception e) {
            throw new BeanDefinitionStoreException(
                    format("%s: encountered during SFig parsing phase",e.getClass().getSimpleName()),e);
        } finally {
            if (runFig != null)
                runFig.close();
        }
    }

    protected int forEach(final RunFig runFig) throws Exception {
        final AtomicInteger beanRegTracker = new AtomicInteger(0);
        final AtomicInteger warnErrTracker = new AtomicInteger(0);
        runFig.forEach(new ProcessOnBeanDef() {
            @Override
            public boolean process(SFigParser.file_return rtn,BeanDef beanDef) throws Exception {
                final String beanName = beanDef.getBeanID();
                log.debug(format("Bean definition: '%s' encountered",beanName));
                Object bean = beanDef.getBeanInstance();
                if (bean == null) {
                    final CommonTree t = beanDef.getAst_node();
                    if (t != null) {
                        final TreeNodeStream treeStrm = new CommonTreeNodeStream(t);
                        final SFigEval walker = new SFigEval(treeStrm);
                        walker.properties = rtn.includeProperties;
                        walker.instances = rtn.preMadeBeansMap;
                        walker.beanDefsMap = rtn.beanDefsMap;
                        bean = walker.beanDef(); // returns a new bean instance
                        if (bean == null) {
                            warnErrTracker.incrementAndGet();
                            log.warn(format("Bean definition %s failed during AST-phase instantiation attempt", beanName));
                            return true;   // is a warn condition so continue processing any subsequent bean definitions
                        }
                    } else {
                        warnErrTracker.incrementAndGet();
                        log.warn(format("Non-instantiated bean definition %s has no AST representation", beanName));
                        return true;   // is a warn condition so continue processing any subsequent bean definitions
                    }
                }
                AbstractBeanDefinition springBeanDef = bean instanceof AbstractBeanDefinition ? (AbstractBeanDefinition) bean : null;
                if (springBeanDef != null) {
                    // clones SFig bean meta definitions into a Spring bean definition context
                    copyToSpringBeanDef(beanDef,springBeanDef);
                    ctx.registerBeanDefinition(beanName, springBeanDef);
                } else {
                    ctx.getBeanFactory().registerSingleton(beanName, bean);
                }
                beanRegTracker.incrementAndGet();
                return true;
            }});
        final int warnErrorsCount = warnErrTracker.get();
        if (warnErrorsCount > 0)
            log.warn(format("%d warn conditions encountered during bean definitions iteration",warnErrorsCount));
        return beanRegTracker.get();
    }
    protected static void copyToSpringBeanDef(BeanDef beanDef,AbstractBeanDefinition springBeanDef) {
        springBeanDef.setScope(beanDef.getScope().getSpringSetting());
        springBeanDef.setAutowireMode(beanDef.getAutoWire().getSpringOrdinal());
        springBeanDef.setLazyInit(beanDef.isLazyInit());
        springBeanDef.setDependencyCheck(beanDef.getDependencyCheck().getSpringOrdinal());
        springBeanDef.setInitMethodName(BeanDef.getMethodName(beanDef.initMethod));
        springBeanDef.setDestroyMethodName(BeanDef.getMethodName(beanDef.destroyMethod));
        springBeanDef.setFactoryMethodName(BeanDef.getMethodName(beanDef.factoryMethod));
        springBeanDef.setFactoryBeanName(beanDef.getFactoryBean());
        if (log.isDebugEnabled())
            log.debug(format("%nBean instance \"%s\":%n%s%n", beanDef.getBeanID(), springBeanDef));
    }
}
